<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

</head>

<body>

    <div id="app">
        <input type="text" name="" id="in" v-model="info">
        {{info}}
    </div>

    <!-- <script>
        var dom = nodeToFragment(document.getElementById('app'))
        console.log(dom)
        
        function nodeToFragment(node){
            var frag = document.createDocumentFragment()        // 创建一个节点容器，可以包含多个子节点，将该节点下所有的子节点劫持获取，不包含自身
            var child;
            while(child = node.firstChild){
                frag.appendChild(child)     // 劫持node的所有子节点
            }
            return frag
        }
    </script> -->

    <script>
        /*
            要实现一个mvvm的双向绑定，必须实现以下几点
            1.实现一个数据监听器Observe,能够对数据对象的所有属性进行监听，如有变动可拿到最新值并通知订阅者Dep
            2.实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析，根据指令模板替换数据，以及绑定响应的更新函数
            3.实现一个观察者Watcher,作为连接Observe和Compile的桥梁，能够订阅并收到每个属性变动的通知，执行指令绑定的响应回调函数，从而更新视图
            4.mvvm入口函数，整合以上三者
        */


        //  数据劫持
        function defineReactive(obj, key, val) {
            var dep = new Dep()
            Object.defineProperty(obj, key, {
                get: function () {
                    // 由于需要在闭包内添加watcher，所以通过Dep定义一个全局target属性，暂存watcher, 添加完移除
                    Dep.target && dep.addSub(Dep.target)
                    return val
                },
                set: function (newVal) {
                    if (newVal === val) return
                    val = newVal
                    dep.notify()        // 通知所有订阅者
                }
            })
        }

        function observe(obj, vm) {
            Object.keys(obj).forEach(function (key) {
                defineReactive(vm, key, obj[key])
            })
        }



        // 解析器（compil nodeToFragment）
        function nodeToFragment(node, vm) {
            var frag = document.createDocumentFragment()
            var child;
            while (child = node.firstChild) {
                compile(child, vm)
                frag.appendChild(child)     // 将node中所有子节点劫持到文档片段中
            }
            return frag
        }
        function compile(node, vm) {
            var reg = /\{\{(.*)\}\}/        // {{  }}  模板字符串匹配规则
            // 节点类型为元素 nodeType:1标签 2属性 3text
            if (node.nodeType === 1) {
                var attr = node.attributes;     // 获取自定义属性
                // 解析属性
                for (var i = 0; i < attr.length; i++) {
                    if (attr[i].nodeName == 'v-model') {    // 遍历属性节点找到v-model的属性
                        var name = attr[i].nodeValue    // 获取v-model绑定的属性名
                        node.addEventListener('input',function(e){
                            // 给相应的data属性赋值，进而触发该属性的set方法
                            vm[name] = e.target.value
                        },false)
                        node.value = vm[name]
                        node.removeAttribute('v-model')
                    }
                }
                new Watcher(vm, node, name, 'value')   // 创建新的watcher，会触发函数向对应属性的dep数组中添加订阅者
            }
            // 节点类型为text
            if (node.nodeType === 3) {
                if (reg.test(node.nodeValue)) {
                    var name = RegExp.$1
                    new Watcher(vm, node, name, 'nodeValue')
                }
            }
        }


        // 观察者
        function Watcher(vm, node, name, type) {
            Dep.target = this
            this.name = name
            this.node = node
            this.vm = vm
            this.type = type
            this.update()
            Dep.target = null
        }
        Watcher.prototype = {
            update: function () {
                this.get()
                this.node[this.type] = this.value       // 订阅者执行相应操作
            },
            get: function () {
                this.value = this.vm[this.name]
            }
        }

        // 订阅者
        function Dep() {
            this.subs = []
        }
        Dep.prototype = {
            addSub: function (sub) {
                this.subs.push(sub)
            },
            notify: function () {
                this.subs.forEach(function (sub) {
                    sub.update()
                })
            }
        }

        function Vue(options) {
            this.data = options.data
            observe(this.data, this)
            var id = options.el
            var dom = nodeToFragment(document.getElementById(id), this)
            // 编译完成后，将dom返回到app中
            document.getElementById(id).appendChild(dom)
        }
        // vue是把data中数据绑定到vue实例上
        var el = new Vue({
            el: 'app',
            data: {
                info: '333'
            }
        })
        setTimeout(()=>{
            el.info = 666
        },10000)
        

    </script>
</body>

</html>